/*
   +----------------------------------------------------------------------+
   | This library is free software; you can redistribute it and/or        |
   | modify it under the terms of the GNU Lesser General Public           |
   | License as published by the Free Software Foundation; either         |
   | version 2.1 of the License, or (at your option) any later version.   | 
   |                                                                      |
   | This library is distributed in the hope that it will be useful,      |
   | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
   | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
   | Lesser General Public License for more details.                      | 
   |                                                                      |
   | You should have received a copy of the GNU Lesser General Public     |
   | License in the file LICENSE along with this library;                 |
   | if not, write to the                                                 | 
   |                                                                      |
   |   Free Software Foundation, Inc.,                                    |
   |   51 Franklin Street, Fifth Floor,                                   |
   |   Boston, MA  02110-1301  USA                                        |
   +----------------------------------------------------------------------+
   | Authors: Alayn Gortazar <zutoin@gmail.com>                           |
   +----------------------------------------------------------------------+
*/

/* $ Id: $ */ 

#include "php_avahii.h"

#if HAVE_AVAHII


/* {{{ phpinfo logo definitions */

#include "php_logos.h"


static unsigned char avahii_logo[] = {
#include "avahii_logos.h"
}; 
/* }}} */

/* {{{ Avahi client functions */
#include <avahi-client/lookup.h>
#include <avahi-common/llist.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/alternative.h>
#include <avahi-common/error.h>
#include <avahi-client/client.h>
#include <avahi-client/publish.h>

typedef enum {
    COMMAND_UNSPEC,
    COMMAND_HELP,
    COMMAND_VERSION,
    COMMAND_PUBLISH_SERVICE,
    COMMAND_PUBLISH_ADDRESS
} Command;

typedef struct Config {
    int verbose;
    Command command;
    uint16_t port;
    AvahiStringList *txt, *subtypes;
    AvahiAddress address;
    int terminate_on_all_for_now;
    int terminate_on_cache_exhausted;
    char *name, *stype, *domain, *host;
    int ignore_local;
    int resolve;
    int no_fail;
    int parsable;
    int no_reverse;
    void *return_value;
} Config;

typedef struct ServiceInfo ServiceInfo;

struct ServiceInfo {
    AvahiIfIndex interface;
    AvahiProtocol protocol;
    char *name, *type, *domain;

    AvahiServiceResolver *resolver;
    Config *config;

    AVAHI_LLIST_FIELDS(ServiceInfo, info);
};

static AvahiClient *client = NULL;
static AvahiSimplePoll *simple_poll = NULL;
static AvahiEntryGroup *entry_group = NULL;
static ServiceInfo *services = NULL;
static int n_all_for_now = 0, n_cache_exhausted = 0, n_resolving = 0;
static AvahiStringList *browsed_types = NULL;

/* {{{ Avahi publish functions */
static int register_stuff(Config *config);

static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
    Config *config = userdata;

    assert(g);
    assert(config);

    switch (state) {

        case AVAHI_ENTRY_GROUP_ESTABLISHED:

            php_printf("Established under name '%s'\n", config->name);
            break;

        case AVAHI_ENTRY_GROUP_FAILURE:

        	php_printf("Failed to register: %s\n", avahi_strerror(avahi_client_errno(client)));
            break;

        case AVAHI_ENTRY_GROUP_COLLISION: {
            char *n;

			n = avahi_alternative_service_name(config->name);

			php_printf("Name collision, picking new name '%s'.\n", n);
            avahi_free(config->name);
            config->name = n;

            register_stuff(config);

            break;
        }

        case AVAHI_ENTRY_GROUP_UNCOMMITED:
        case AVAHI_ENTRY_GROUP_REGISTERING:
            ;
    }
}

static int register_stuff(Config *config) {
    assert(config);

    if (!entry_group) {
        if (!(entry_group = avahi_entry_group_new(client, entry_group_callback, config))) {
        	php_printf("Failed to create entry group: %s\n", avahi_strerror(avahi_client_errno(client)));
            return -1;
        }
    }

    assert(avahi_entry_group_is_empty(entry_group));

	AvahiStringList *i;

	assert(config->command == COMMAND_PUBLISH_SERVICE);

	if (avahi_entry_group_add_service_strlst(entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, config->name, config->stype, config->domain, config->host, config->port, config->txt) < 0) {
		php_printf("Failed to add service: %s\n", avahi_strerror(avahi_client_errno(client)));
		return -1;
	}

	for (i = config->subtypes; i; i = i->next)
		if (avahi_entry_group_add_service_subtype(entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, config->name, config->stype, config->domain, (char*) i->text) < 0) {
			php_printf("Failed to add subtype '%s': %s\n", i->text, avahi_strerror(avahi_client_errno(client)));
			return -1;
		}

    avahi_entry_group_commit(entry_group);

    return 0;
}

static void publish_client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
    Config *config = userdata;

    client = c;

    switch (state) {
        case AVAHI_CLIENT_FAILURE:

            if (config->no_fail && avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
                int error;

                /* We have been disconnected, so let reconnect */

                php_printf("Disconnected, reconnecting ...\n");

                avahi_client_free(client);
                client = NULL;
                entry_group = NULL;

                if (!(client = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_NO_FAIL, publish_client_callback, config, &error))) {
                	php_printf("Failed to create client object: %s\n", avahi_strerror(error));
                    avahi_simple_poll_quit(simple_poll);
                }

            } else {
            	php_printf("Client failure, exiting: %s\n", avahi_strerror(avahi_client_errno(c)));
                avahi_simple_poll_quit(simple_poll);
            }

            break;

        case AVAHI_CLIENT_S_RUNNING:

            if (register_stuff(config) < 0)
                avahi_simple_poll_quit(simple_poll);

            break;

        case AVAHI_CLIENT_S_COLLISION:

            if (config->verbose)
            	php_printf("Host name conflict\n");

            /* Fall through */

        case AVAHI_CLIENT_S_REGISTERING:

            if (entry_group) {
                avahi_entry_group_free(entry_group);
                entry_group = NULL;
            }
            break;

        case AVAHI_CLIENT_CONNECTING:

            if (config->verbose)
            	php_printf("Waiting for daemon ...\n");

            break;

            ;
    }
}
/* }}} Avahi publish functions */

/* }}} Avahi client functions */

/* {{{ Class definitions */

/* {{{ Class Avahi_Service */

static zend_object_handlers Avahi_Service_handlers;
static zend_class_entry * Avahi_Service_ce_ptr = NULL;

/* {{{ static zend_object_value Avahi_Service_object_new_ex(zend_class_entry *class_type, Avahi_Service_object **obj TSRMLS_DC)
   */
static zend_object_value Avahi_Service_object_new_ex(zend_class_entry *class_type, Avahi_Service_object **obj TSRMLS_DC)
{
	Avahi_Service_object *intern;
	zend_object_value retval;
	zval *tmp;

	intern = emalloc(sizeof(Avahi_Service_object));
	memset(intern, 0, sizeof(Avahi_Service_object));
	if (obj) {
		*obj = intern;
	}

	zend_object_std_init(&intern->std, class_type TSRMLS_CC);
	zend_hash_copy(intern->std.properties, &class_type->default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));

	retval.handle = zend_objects_store_put(intern, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)zend_objects_free_object_storage, NULL TSRMLS_CC);
	retval.handlers = &Avahi_Service_handlers;
	return retval;
}
/* }}} Avahi_Service_object_new_ex */

/* {{{ static zend_object_value Avahi_Service_object_new(zend_class_entry *class_type TSRMLS_DC)
   */
static zend_object_value Avahi_Service_object_new(zend_class_entry *class_type TSRMLS_DC)
{
	Avahi_Service_object *tmp;
	return Avahi_Service_object_new_ex(class_type, &tmp TSRMLS_CC);
}

static zval * Avahi_Service_instanciate(zend_class_entry *ce_ptr, zval *service TSRMLS_DC)
{
	/*
	if (!service) {
	}
	*/
	MAKE_STD_ZVAL(service);
	object_init_ex(service, ce_ptr);
	return service;
}

/* }}} Avahi_Service_object_new */
static void check_terminate(Config *c) {

    assert(n_all_for_now >= 0);
    assert(n_cache_exhausted >= 0);
    assert(n_resolving >= 0);

    if (n_all_for_now <= 0 && n_resolving <= 0) {

        if (c->terminate_on_all_for_now) {
            avahi_simple_poll_quit(simple_poll);
        }
    }

    if (n_cache_exhausted <= 0 && n_resolving <= 0) {

        if (c->terminate_on_cache_exhausted) {
            avahi_simple_poll_quit(simple_poll);
        }
    }
}

static void service_resolver_callback(
    AvahiServiceResolver *r,
    AvahiIfIndex interface,
    AvahiProtocol protocol,
    AvahiResolverEvent event,
    const char *name,
    const char *type,
    const char *domain,
    const char *host_name,
    const AvahiAddress *a,
    uint16_t port,
    AvahiStringList *txt,
    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
    void *userdata) {

    ServiceInfo *i = userdata;
    char address[AVAHI_ADDRESS_STR_MAX], *t;

    zval *service;

    assert(r);
    assert(i);

    switch (event) {
        case AVAHI_RESOLVER_FOUND:

            avahi_address_snprint(address, sizeof(address), a);

            t = avahi_string_list_to_string(txt);

            /**
             * We add service data to returned array
             */

            service = Avahi_Service_instanciate(Avahi_Service_ce_ptr, service TSRMLS_CC);
            zend_update_property_string(Avahi_Service_ce_ptr, service, "name", sizeof("name") - 1, i->name TSRMLS_CC);
            zend_update_property_string(Avahi_Service_ce_ptr, service, "type", sizeof("type") - 1, i->type TSRMLS_CC);
            zend_update_property_string(Avahi_Service_ce_ptr, service, "domain", sizeof("domain") - 1, i->domain TSRMLS_CC);
            zend_update_property_string(Avahi_Service_ce_ptr, service, "hostname", sizeof("hostname") - 1, host_name TSRMLS_CC);
            zend_update_property_string(Avahi_Service_ce_ptr, service, "address", sizeof("address") - 1, address TSRMLS_CC);
            zend_update_property_long(Avahi_Service_ce_ptr, service, "port", sizeof("port") - 1, port TSRMLS_CC);
            zend_update_property_string(Avahi_Service_ce_ptr, service, "txt", sizeof("txt") - 1, t TSRMLS_CC);
			add_next_index_zval(i->config->return_value, service);
            avahi_free(t);
            break;

        case AVAHI_RESOLVER_FAILURE:
            php_printf("Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(client)));
            break;
    }

    avahi_service_resolver_free(i->resolver);
    i->resolver = NULL;

    assert(n_resolving > 0);
    n_resolving--;
    check_terminate(i->config);
}

static void remove_service(ServiceInfo *i) {
    assert(i);

    AVAHI_LLIST_REMOVE(ServiceInfo, info, services, i);

    if (i->resolver) {
        avahi_service_resolver_free(i->resolver);
    }

    avahi_free(i->name);
    avahi_free(i->type);
    avahi_free(i->domain);
    avahi_free(i);
}

static ServiceInfo *add_service(Config *c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
    ServiceInfo *i;
    i = avahi_new(ServiceInfo, 1);

    if (c->resolve) {
        i->resolver = avahi_service_resolver_new(client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, service_resolver_callback, i);
        if (!(i->resolver)) {
            avahi_free(i);
            php_printf("Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(client)));
            return NULL;
        }
        n_resolving++;
    } else {
        i->resolver = NULL;
    }

    i->interface = interface;
    i->protocol = protocol;
    i->name = avahi_strdup(name);
    i->type = avahi_strdup(type);
    i->domain = avahi_strdup(domain);
    i->config = c;

    AVAHI_LLIST_PREPEND(ServiceInfo, info, services, i);

    return i;
}

static browse_callback(

    AvahiServiceBrowser *b,
    AvahiIfIndex interface,
    AvahiProtocol protocol,
    AvahiBrowserEvent event,
    const char *name,
    const char *type,
    const char *domain,
    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
    void* userdata) {
    Config *c = userdata;
    assert(b);

    switch (event) {
        case AVAHI_BROWSER_FAILURE:
            php_printf("(Browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
            avahi_simple_poll_quit(simple_poll);
            return;

        case AVAHI_BROWSER_NEW:
        	add_service(c, interface, protocol, name, type, domain);
            break;

        case AVAHI_BROWSER_REMOVE:
            php_printf("(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
            break;

        case AVAHI_BROWSER_CACHE_EXHAUSTED:
            n_cache_exhausted --;
            check_terminate(c);
            break;

        case AVAHI_BROWSER_ALL_FOR_NOW:
            n_all_for_now --;
            check_terminate(c);
            break;
    }
}

static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
    assert(c);

    switch (state) {
	case  AVAHI_CLIENT_FAILURE:
    	    php_printf("Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c)));
            avahi_simple_poll_quit(simple_poll);
	    break;
    }
}

/*
 * {{{ proto string browse_service_type(Config * config, string type, string domain [optional])
 * Return an array with all the services of 'type' running on the 'domain'
 */
static void browse_service_type(Config * config, char *type, char *domain)
{
    AvahiServiceBrowser *sb = NULL;
    AvahiStringList *i;

    for (i = browsed_types; i; i = i->next)
        if (avahi_domain_equal(type, (char*) i->text))
            return;


	/* Create the service browser */
    if (!(sb = avahi_service_browser_new(
    		client,
    		AVAHI_IF_UNSPEC,
    		AVAHI_PROTO_UNSPEC,
    		type,
    		domain,
    		0,
    		browse_callback,
    		config))) {
    	/*
    	 * TODO: Here an exception should be returned
    	 */
        php_printf("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client)));
        return;
    }

    browsed_types = avahi_string_list_add(browsed_types, type);

    n_all_for_now++;
    n_cache_exhausted++;
}
/*
 * }}}
 */

static void service_type_browser_callback(
    AvahiServiceTypeBrowser *b,
    AVAHI_GCC_UNUSED AvahiIfIndex interface,
    AVAHI_GCC_UNUSED AvahiProtocol protocol,
    AvahiBrowserEvent event,
    const char *type,
    const char *domain,
    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
    void *userdata) {

    Config *c = userdata;

    switch (event) {

        case AVAHI_BROWSER_NEW:
            browse_service_type(c, type, domain);
            break;

        case AVAHI_BROWSER_REMOVE:
            /* We're dirty and never remove the browser again */
            break;

        case AVAHI_BROWSER_FAILURE:
        	php_printf("service_type_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
            avahi_simple_poll_quit(simple_poll);
            break;

        case AVAHI_BROWSER_CACHE_EXHAUSTED:
            n_cache_exhausted --;
            check_terminate(c);
            break;

        case AVAHI_BROWSER_ALL_FOR_NOW:
            n_all_for_now --;
            check_terminate(c);
            break;
    }
}

static void browse_all(Config * config, char *domain) {
    AvahiServiceTypeBrowser *b;

    if (!(b = avahi_service_type_browser_new(
              client,
              AVAHI_IF_UNSPEC,
              AVAHI_PROTO_UNSPEC,
              domain,
              0,
              service_type_browser_callback,
              config))) {

        php_printf("avahi_service_type_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
        return;
    }
    n_all_for_now++;
    n_cache_exhausted++;
}

/* {{{ Methods */


/* {{{ proto string getName()
   */
PHP_METHOD(Avahi_Service, getName)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Avahi_Service_ce_ptr) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zval * _name = NULL;

		_name = zend_read_property(_this_ce, _this_zval, "name", sizeof("name")-1, 1 TSRMLS_CC);
		RETURN_STRING(Z_STRVAL_P(_name), 1);
	} while (0);
}
/* }}} getName */



/* {{{ proto object setName(string name)
   */
PHP_METHOD(Avahi_Service, setName)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;
	const char * name = NULL;
	int name_len = 0;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Avahi_Service_ce_ptr, &name, &name_len) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zend_update_property_string(_this_ce, _this_zval, "name", sizeof("name")-1, name TSRMLS_CC);
		RETURN_ZVAL(_this_zval, 1, 0);
	} while (0);
}
/* }}} setName */



/* {{{ proto string getType()
   */
PHP_METHOD(Avahi_Service, getType)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Avahi_Service_ce_ptr) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zval * _type = NULL;

		_type = zend_read_property(_this_ce, _this_zval, "type", sizeof("type")-1, 1 TSRMLS_CC);
		RETURN_STRING(Z_STRVAL_P(_type), 1);
	} while (0);
}
/* }}} getType */



/* {{{ proto object setType(string type)
   */
PHP_METHOD(Avahi_Service, setType)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;
	const char * type = NULL;
	int type_len = 0;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Avahi_Service_ce_ptr, &type, &type_len) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zend_update_property_string(_this_ce, _this_zval, "type", sizeof("type")-1, type TSRMLS_CC);
		RETURN_ZVAL(_this_zval, 1, 0);
	} while (0);
}
/* }}} setType */



/* {{{ proto string getDomain()
   */
PHP_METHOD(Avahi_Service, getDomain)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Avahi_Service_ce_ptr) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zval * _domain = NULL;

		_domain = zend_read_property(_this_ce, _this_zval, "domain", sizeof("domain")-1, 1 TSRMLS_CC);
		RETURN_STRING(Z_STRVAL_P(_domain), 1);
	} while (0);
}
/* }}} getDomain */



/* {{{ proto object setDomain(string domain)
   */
PHP_METHOD(Avahi_Service, setDomain)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;
	const char * domain = NULL;
	int domain_len = 0;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Avahi_Service_ce_ptr, &domain, &domain_len) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zend_update_property_string(_this_ce, _this_zval, "domain", sizeof("domain")-1, domain TSRMLS_CC);
		RETURN_ZVAL(_this_zval, 1, 0);
	} while (0);
}
/* }}} setDomain */



/* {{{ proto string getHostname()
   */
PHP_METHOD(Avahi_Service, getHostname)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Avahi_Service_ce_ptr) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zval * _hostname = NULL;

		_hostname = zend_read_property(_this_ce, _this_zval, "hostname", sizeof("hostname")-1, 1 TSRMLS_CC);
		RETURN_STRING(Z_STRVAL_P(_hostname), 1);
	} while (0);
}
/* }}} getHostname */



/* {{{ proto object setHostname(string hostname)
   */
PHP_METHOD(Avahi_Service, setHostname)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;
	const char * hostname = NULL;
	int hostname_len = 0;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Avahi_Service_ce_ptr, &hostname, &hostname_len) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zend_update_property_string(_this_ce, _this_zval, "hostname", sizeof("hostname")-1, hostname TSRMLS_CC);
		RETURN_ZVAL(_this_zval, 1, 0);
	} while (0);
}
/* }}} setHostname */



/* {{{ proto string getAddress()
   */
PHP_METHOD(Avahi_Service, getAddress)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Avahi_Service_ce_ptr) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zval * _address = NULL;

		_address = zend_read_property(_this_ce, _this_zval, "address", sizeof("address")-1, 1 TSRMLS_CC);
		RETURN_STRING(Z_STRVAL_P(_address), 1);
	} while (0);
}
/* }}} getAddress */



/* {{{ proto object setAddress(string address)
   */
PHP_METHOD(Avahi_Service, setAddress)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;
	const char * address = NULL;
	int address_len = 0;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Avahi_Service_ce_ptr, &address, &address_len) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zend_update_property_string(_this_ce, _this_zval, "address", sizeof("address")-1, address TSRMLS_CC);
		RETURN_ZVAL(_this_zval, 1, 0);
	} while (0);
}
/* }}} setAddress */



/* {{{ proto int getPort()
   */
PHP_METHOD(Avahi_Service, getPort)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Avahi_Service_ce_ptr) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zval * _port = NULL;

		_port = zend_read_property(_this_ce, _this_zval, "port", sizeof("port")-1, 1 TSRMLS_CC);
		RETURN_LONG(Z_LVAL_P(_port));
	} while (0);
}
/* }}} getPort */



/* {{{ proto object setPort(int port)
   */
PHP_METHOD(Avahi_Service, setPort)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;
	long port = 0;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &_this_zval, Avahi_Service_ce_ptr, &port) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zend_update_property_long(_this_ce, _this_zval, "port", sizeof("port")-1, port TSRMLS_CC);
		RETURN_ZVAL(_this_zval, 1, 0);
	} while (0);
}
/* }}} setPort */



/* {{{ proto string getTxt()
   */
PHP_METHOD(Avahi_Service, getTxt)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Avahi_Service_ce_ptr) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zval * _txt = NULL;

		_txt = zend_read_property(_this_ce, _this_zval, "txt", sizeof("txt")-1, 1 TSRMLS_CC);
		RETURN_STRING(Z_STRVAL_P(_txt), 1);
	} while (0);
}
/* }}} getTxt */



/* {{{ proto object setTxt(string txt)
   */
PHP_METHOD(Avahi_Service, setTxt)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;
	const char * txt = NULL;
	int txt_len = 0;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &_this_zval, Avahi_Service_ce_ptr, &txt, &txt_len) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	do {
		zend_update_property_string(_this_ce, _this_zval, "txt", sizeof("txt")-1, txt TSRMLS_CC);
		RETURN_ZVAL(_this_zval, 1, 0);
	} while (0);
}
/* }}} setTxt */



/* {{{ proto bool publish()
   */
PHP_METHOD(Avahi_Service, publish)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &_this_zval, Avahi_Service_ce_ptr) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);

    int ret = 1, error;
    Config config;
    const char *argv0;
    char *e;
    long int p;
    int i;

    config.verbose = config.no_fail = config.no_reverse = 0;
    config.host = config.name = config.domain = config.stype = NULL;
    config.port = 0;
    config.txt = config.subtypes = NULL;
    config.command = COMMAND_PUBLISH_SERVICE;

	config.name = Z_STRVAL_P(zend_read_property(_this_ce, _this_zval, "name", sizeof("name")-1, 1 TSRMLS_CC));
	config.stype = Z_STRVAL_P(zend_read_property(_this_ce, _this_zval, "type", sizeof("type")-1, 1 TSRMLS_CC));
	config.port = Z_LVAL_P(zend_read_property(_this_ce, _this_zval, "port", sizeof("port")-1, 1 TSRMLS_CC));

	/*
     * ALAYN
     * Esto juraría que coge un string y lo añade al c.txt
     * Cambiarlo para que coja los datos del array que recibiremos en txt como parametro
     */
    /*
    for (i = optind+3; i < argc; i++)
        config.txt = avahi_string_list_add(config.txt, argv[i]);
	*/

//    config.txt = zend_read_property(_this_ce, _this_zval, "txt", sizeof("txt")-1, 1 TSRMLS_CC);
    config.txt = avahi_string_list_add(config.txt, "baz ofia");

    php_printf("\n\n\n%s\n%s\n%u\n%s\n", config.name, config.stype, config.port, avahi_string_list_to_string(config.txt));
    errno = 0;

	if (!(simple_poll = avahi_simple_poll_new())) {
		/*
		 * Here an exception should be thrown
		 */
		php_printf("Failed to create simple poll object.\n");
		RETURN_FALSE;
	}

	/*
	if (sigint_install(simple_poll) < 0)
		RETURN_FALSE;
	*/

	if (!(client = avahi_client_new(avahi_simple_poll_get(simple_poll), config.no_fail ? AVAHI_CLIENT_NO_FAIL : 0, publish_client_callback, &config, &error))) {
		php_printf("Failed to create client object: %s\n", avahi_strerror(error));
		RETURN_FALSE;
	}

	/**
	 * ALAYN
	 * This may be used for a debug mode
	 */
	if (avahi_client_get_state(client) != AVAHI_CLIENT_CONNECTING && config.verbose) {
		const char *version, *hn;

		if (!(version = avahi_client_get_version_string(client))) {
			php_printf("Failed to query version string: %s\n", avahi_strerror(avahi_client_errno(client)));
			RETURN_FALSE;
		}

		if (!(hn = avahi_client_get_host_name_fqdn(client))) {
			php_printf("Failed to query host name: %s\n", avahi_strerror(avahi_client_errno(client)));
			RETURN_FALSE;
		}

		php_printf("Server version: %s; Host name: %s\n", version, hn);
	}

	avahi_simple_poll_loop(simple_poll);

	RETURN_TRUE;
}
/* }}} publish */


static zend_function_entry Avahi_Service_methods[] = {
	PHP_ME(Avahi_Service, getName, NULL, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, setName, Avahi_Service__setName_args, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, getType, NULL, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, setType, Avahi_Service__setType_args, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, getDomain, NULL, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, setDomain, Avahi_Service__setDomain_args, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, getHostname, NULL, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, setHostname, Avahi_Service__setHostname_args, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, getAddress, NULL, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, setAddress, Avahi_Service__setAddress_args, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, getPort, NULL, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, setPort, Avahi_Service__setPort_args, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, getTxt, NULL, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, setTxt, Avahi_Service__setTxt_args, /**/ZEND_ACC_PUBLIC)
	PHP_ME(Avahi_Service, publish, NULL, /**/ZEND_ACC_PUBLIC)
	{ NULL, NULL, NULL }
};

/* }}} Methods */

static void class_init_Avahi_Service(void)
{
	zend_class_entry ce;

	INIT_CLASS_ENTRY(ce, "Avahi_Service", Avahi_Service_methods);
	ce.create_object = Avahi_Service_object_new;
	Avahi_Service_ce_ptr = zend_register_internal_class(&ce);
	memcpy(&Avahi_Service_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));

	/* {{{ Property registration */

	zend_declare_property_string(Avahi_Service_ce_ptr, 
		"name", 4, "", 
		ZEND_ACC_PROTECTED TSRMLS_DC);

	zend_declare_property_string(Avahi_Service_ce_ptr, 
		"type", 4, "", 
		ZEND_ACC_PROTECTED TSRMLS_DC);

	zend_declare_property_string(Avahi_Service_ce_ptr, 
		"domain", 6, "", 
		ZEND_ACC_PROTECTED TSRMLS_DC);

	zend_declare_property_string(Avahi_Service_ce_ptr, 
		"hostname", 8, "", 
		ZEND_ACC_PROTECTED TSRMLS_DC);

	zend_declare_property_string(Avahi_Service_ce_ptr, 
		"address", 7, "", 
		ZEND_ACC_PROTECTED TSRMLS_DC);

	zend_declare_property_long(Avahi_Service_ce_ptr, 
		"port", 4, 0, 
		ZEND_ACC_PROTECTED TSRMLS_DC);

	zend_declare_property_string(Avahi_Service_ce_ptr, 
		"txt", 3, "", 
		ZEND_ACC_PROTECTED TSRMLS_DC);

	/* }}} Property registration */

}

/* }}} Class Avahi_Service */

/* {{{ Class Avahi_ServiceBrowser */

static zend_class_entry * Avahi_ServiceBrowser_ce_ptr = NULL;

/* {{{ Methods */


/* {{{ proto array browse([string type = ''[, string domain = 'local']])
  Return an array with all services of 'type' running on the 'domain' */
PHP_METHOD(Avahi_ServiceBrowser, browse)
{
	zend_class_entry * _this_ce;

	zval * _this_zval = NULL;
	const char * type = "";
	int type_len = 0;
	const char * domain = "local";
	int domain_len = 5;



	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|ss", &_this_zval, Avahi_ServiceBrowser_ce_ptr, &type, &type_len, &domain, &domain_len) == FAILURE) {
		return;
	}

	_this_ce = Z_OBJCE_P(_this_zval);


	array_init(return_value);

	int error;
	Config config;
    config.return_value = return_value;
    config.verbose = 0;
    config.resolve = 1;
    config.terminate_on_cache_exhausted = 1;
    config.terminate_on_all_for_now = 1;

    /* Allocate main loop object */
    if (!(simple_poll = avahi_simple_poll_new())) {
    	/*
    	 * TODO: Here an exception should be returned
    	 */
        php_printf("Failed to create simple poll object.\n");
        return;
    }

	/* Ensure that services are empty */
    while (services) {
        remove_service(services);
    }

    avahi_string_list_free(browsed_types);
    browsed_types = NULL;

    /* Allocate a new client */
    client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, client_callback, &config, &error);

    /* Check wether creating the client object succeeded */
    if (!client) {
    	/*
    	 * TODO: Here an exception should be returned
    	 */
        php_printf("Failed to create client: %s\n", avahi_strerror(error));
        return;
    }

	if (type_len > 0) {
		browse_service_type(&config, type, domain);
	} else {
		browse_all(&config, domain);
	}

    /* Run the main loop */
    avahi_simple_poll_loop(simple_poll);

}
/* }}} browse */


static zend_function_entry Avahi_ServiceBrowser_methods[] = {
	PHP_ME(Avahi_ServiceBrowser, browse, Avahi_ServiceBrowser__browse_args, /**/ZEND_ACC_PUBLIC)
	{ NULL, NULL, NULL }
};

/* }}} Methods */

static void class_init_Avahi_ServiceBrowser(void)
{
	zend_class_entry ce;

	INIT_CLASS_ENTRY(ce, "Avahi_ServiceBrowser", Avahi_ServiceBrowser_methods);
	Avahi_ServiceBrowser_ce_ptr = zend_register_internal_class(&ce);
}

/* }}} Class Avahi_ServiceBrowser */

/* }}} Class definitions*/

/* {{{ avahii_functions[] */
function_entry avahii_functions[] = {
	PHP_MALIAS(Avahi_ServiceBrowser, Avahi_ServiceBrowser_browse, browse, NULL, ZEND_ACC_PUBLIC)
	{ NULL, NULL, NULL }
};
/* }}} */


/* {{{ avahii_module_entry
 */
zend_module_entry avahii_module_entry = {
	STANDARD_MODULE_HEADER,
	"avahii",
	avahii_functions,
	PHP_MINIT(avahii),     /* Replace with NULL if there is nothing to do at php startup   */ 
	PHP_MSHUTDOWN(avahii), /* Replace with NULL if there is nothing to do at php shutdown  */
	PHP_RINIT(avahii),     /* Replace with NULL if there is nothing to do at request start */
	PHP_RSHUTDOWN(avahii), /* Replace with NULL if there is nothing to do at request end   */
	PHP_MINFO(avahii),
	"0.1", 
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_AVAHII
ZEND_GET_MODULE(avahii)
#endif


/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(avahii)
{
	php_register_info_logo("AVAHII_LOGO_ID", "", avahii_logo, 29810);
	class_init_Avahi_Service();
	class_init_Avahi_ServiceBrowser();

	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(avahii)
{
	php_unregister_info_logo("AVAHII_LOGO_ID");

	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(avahii)
{
	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_RSHUTDOWN_FUNCTION */
PHP_RSHUTDOWN_FUNCTION(avahii)
{
	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(avahii)
{
	php_info_print_box_start(0);

	php_printf("<img src='");
	if (SG(request_info).request_uri) {
		php_printf("%s", (SG(request_info).request_uri));
	}   
	php_printf("?=%s", "AVAHII_LOGO_ID");
	php_printf("' align='right' alt='image' border='0'>\n");

	php_printf("<p>avahii extension</p>\n");
	php_printf("<p>Version 0.1devel (2010-08-25)</p>\n");
	php_printf("<p><b>Authors:</b></p>\n");
	php_printf("<p>Alayn Gortazar &lt;zutoin@gmail.com&gt; (lead)</p>\n");
	php_info_print_box_end();
	do {
	} while (0);

}
/* }}} */

#endif /* HAVE_AVAHII */


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
